#!/usr/bin/python
#***************************************************************************
# Copyright notice for this file:
#  Copyright (C) 2012 TheAxeMan
#  _______ _                               _    _
# |__   __| |     ___     /\          ___ | \  / |
#    | |  | |__  / _ \   /  \  __  _ / _ \|  \/  | __ _ _ __
#    | |  | '_ \|  __/  / /\ \ \ \/ |  __/| .  . |/ _` | '_ \
#    | |  | | | | (__/\/ ____ \ )  (| (__/| |\/| | (_| | | | |
#    |_|  |_| |_|\____/_/    \_._/\_.\____|_|  |_|\__,_|_| |_|
#
# Final Fantasy TAS scripts by TheAxeMan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#***************************************************************************


Fm2Header = """version 3
emuVersion 21050
rerecordCount 0
palFlag 0
romFilename Final Fantasy (U)
romChecksum base64:JK5e34N1Fi+RpoRtMgLj1g==
guid 2CFDB3FA-742C-CDF6-CEE0-C124FA6A71C1
fourscore 0
microphone 0
port0 1
port1 0
port2 0
FDS 0
NewPPU 0
comment author TheAxeMan
"""

Keyboard = """
ABCDEFGHIJ
KLMNOPQRST
UVWXYZ',. 
0123456789
abcdefghij
klmnopqrst
uvwxyz-:!?
""".strip()
Keyboard = [[s[i] for i in xrange(len(s))] for s in Keyboard.split('\n')]
ValidChars = set()
for row in Keyboard:
   ValidChars = ValidChars.union(row)

CharCoordMap = {}
for (rowNum, row) in enumerate(Keyboard):
   for (colNum, char) in enumerate(row):
      CharCoordMap[char] = (rowNum, colNum)


class CharType(object):
   fighter, thief, blackbelt, redmage, whitemage, blackmage = range(6)
   
def pressButtons(**kw):
   '''Generate one frame from dict with buttons pressed. Doesn't handle reset or power.'''
   frame = '|0|'
   for button in ['R','L','D','U','T','S','B','A']:
      frame += button if button in kw else '.'
   return frame + '|||\n'

def pressStart():
   return pressButtons(T=1)
def pressA():
   return pressButtons(A=1)
def pressUp():
   return pressButtons(U=1)
def pressDown():
   return pressButtons(D=1)
def pressLeft():
   return pressButtons(L=1)
def pressRight():
   return pressButtons(R=1)
         
def waitFrames(n=1):
   s = ""
   for i in xrange(n):
      s += pressButtons()
   return s

def pickChar(currType, desiredType):
   '''Start off when ready to hit left or right to pick char class. Generates
      input to pick the class and get to name entry screen'''
   if currType == desiredType:
      return waitFrames() + pressA()
   
   input = ""
   leftNotRight = True
   while currType != desiredType:
      if leftNotRight:
         input += pressLeft()
      else:
         input += pressRight()
      leftNotRight = not leftNotRight
      input += waitFrames(2)
      currType = (currType + 1) % 6
      
   return input + pressA()

def getInputPlan(name):
   up, down, left, right = range(4)
   numRows = len(Keyboard)
   numCols = len(Keyboard[0])
   currRow = 0
   currCol = 0
   possibleWays = []
   
   #First we compile of list of possible directions to start and finish cursor movement without
   #sacrificing frames.
   for char in [name[0], name[1], name[2], name[3]]:
      goalRow, goalCol = CharCoordMap[char]
      
      spacesUp = (currRow - goalRow + numRows) % numRows
      spacesDown = (goalRow - currRow + numRows) % numRows
      spacesLeft = (currCol - goalCol + numCols) % numCols
      spacesRight = (goalCol - currCol + numCols) % numCols
      
      hMove = min(spacesLeft, spacesRight)
      vMove = min(spacesUp, spacesDown)
      
      possibleWaysThisChar = []
      if hMove > vMove:
         if spacesLeft <= spacesRight:
            possibleWaysThisChar.append( (left, left) )
         if spacesLeft >= spacesRight:
            possibleWaysThisChar.append( (right, right) )
      elif vMove > hMove:
         if spacesUp <= spacesDown:
            possibleWaysThisChar.append( (up, up) )
         if spacesUp >= spacesDown:
            possibleWaysThisChar.append( (down, down) )
      else:
         upOrDown = spacesUp == spacesDown
         leftOrRight = spacesLeft == spacesRight
            
         if upOrDown and leftOrRight:
            #Ok, so this can't happen in the Final Fantasy menu setup. But obviously if I am
            #making TASes I am a perfectionist.
            possibleWaysThisChar = [(up, right), (up, left), (down, right), (down, left),
                                    (right, up), (right, down), (left, up), (left, down)]
         elif upOrDown:
            hway = left if spacesLeft < spacesRight else right
            possibleWaysThisChar = [(up, hway), (down, hway), (hway, up), (hway, down)]
         
         elif leftOrRight:
            vway = up if spacesUp < spacesDown else down
            possibleWaysThisChar = [(left, vway), (right, vway), (vway, left), (vway, right)]
            
         else:
            hway = left if spacesLeft < spacesRight else right
            vway = up if spacesUp < spacesDown else down
            possibleWaysThisChar = [(hway, vway), (vway, hway)]
            
      possibleWays.append(possibleWaysThisChar)
      currRow, currCol = (goalRow, goalCol)
      
   #Now find a way that avoids frame loss from starting on direction just ended.
   pick = [0 for i in xrange(len(possibleWays))]
   bestPick = pick[:]
   bestConflicts = len(possibleWays)
   while True:
      numConflicts = 0
      for i in xrange(len(possibleWays)):
         currWay = possibleWays[i][pick[i]]
         if i > 0 and lastWay[1] == currWay[0]:
            numConflicts += 1
         lastWay = currWay
      if numConflicts < bestConflicts:
         bestConflicts = numConflicts
         bestPick = pick[:]
         #Quick exit if we have found a perfect combination
         if numConflicts == 0:
            break
      #move to next pick
      incrIndex = None
      for i in xrange(len(pick)):
         if pick[i]+1 < len(possibleWays[i]):
            incrIndex = i
            break
      if incrIndex is None:
         break
      pick[incrIndex] += 1
      for i in xrange(incrIndex):
         pick[i] = 0
      
   return [possibleWays[i][bestPick[i]] for i in xrange(len(possibleWays))]
   
def inputName(name):
   '''Start on first no-lag frame after picking class. Pick name and then hit A
      to finalize. Chars in name better be on Keyboard.'''
   numRows = len(Keyboard)
   numCols = len(Keyboard[0])
   currRow = 0
   currCol = 0
   
   while len(name) < 4:
      name += " "
      
   input = ""
   inputPlan = getInputPlan(name)
   up, down, left, right = range(4)
   lastDir = None
   
   for i, char in enumerate([name[i] for i in xrange(4)]):
      goalRow, goalCol = CharCoordMap[char]
      start, end = inputPlan[i]
      
      moveHorzNotVert = start in [left, right]
         
      if up in [start, end]:
         vDir = up
      elif down in [start, end]:
         vDir = down
      else:
         spacesUp = (currRow - goalRow + numRows) % numRows
         spacesDown = (goalRow - currRow + numRows) % numRows
         vDir = up if spacesUp < spacesDown else down
      
      if left in [start, end]:
         hDir = left
      elif right in [start, end]:
         hDir = right
      else:
         spacesLeft = (currCol - goalCol + numCols) % numCols
         spacesRight = (goalCol - currCol + numCols) % numCols
         hDir = left if spacesLeft < spacesRight else right
      
      while True:
         if moveHorzNotVert:
            if currCol == goalCol:
               input += waitFrames()
               lastDir = None
            elif hDir == left:
               if lastDir == left:
                  input += waitFrames()
               input += pressLeft()
               currCol = (currCol - 1 + numCols) % numCols
               lastDir = left
            else:
               if lastDir == right:
                  input += waitFrames()
               input += pressRight()
               currCol = (currCol + 1) % numCols
               lastDir = right
         else:
            if currRow == goalRow:
               input += waitFrames()
               lastDir = None
            elif vDir == up:
               if lastDir == up:
                  input += waitFrames()
               input += pressUp()
               currRow = (currRow - 1 + numRows) % numRows
               lastDir = up
            else:
               if lastDir == down:
                  input += waitFrames()
               input += pressDown()
               currRow = (currRow + 1) % numRows
               lastDir = down
               
         if (currRow, currCol) == (goalRow, goalCol):
            break
         moveHorzNotVert = not moveHorzNotVert
         
      #Once on the right spot, hit A and wait past a lag frame
      input += pressA() + waitFrames()
      
   #After last char, wait another frame and hit A
   input += waitFrames() + pressA()
   
   return input

def getRunThroughNames(names, charClass=CharType.fighter, subtitles=""):
   '''Gets the start of the run, ending with the A button confirming party selection'''
   fm2File = Fm2Header
   fm2File += subtitles
   fm2File += waitFrames(10) + pressStart()
   fm2File += waitFrames(6) + pressLeft() + pressStart()
   fm2File += waitFrames(6)
   #Special tweak: If class is fighter, the first char starts on fighter.
   #We can just hit A without waiting to register a blank.
   #After the first char we need a wait frame.
   if charClass == CharType.fighter:
      fm2File += pressA()
   else:
      fm2File += pickChar(CharType.fighter, charClass)
   fm2File += waitFrames()
   
   fm2File += inputName(names[0]) + waitFrames()
   fm2File += pickChar(CharType.thief, charClass) + waitFrames()
   fm2File += inputName(names[1]) + waitFrames()
   fm2File += pickChar(CharType.blackbelt, charClass) + waitFrames()
   fm2File += inputName(names[2]) + waitFrames()
   fm2File += pickChar(CharType.redmage, charClass) + waitFrames()
   fm2File += inputName(names[3]) + waitFrames(3) + pressA()
   
   return fm2File

def main():
   #names = ["Wedg", "Bigs", "Axe ", "Viks"]
   #fm2Data = getRunThroughNames(names)
   #names = ["IsSq", "uare", "orld", "TheW"]
   #fm2Data = getRunThroughNames(names, charClass=CharType.whitemage)
   names = ["AAAA" for i in range(4)]
   fm2Data = getRunThroughNames(names)
   
   fm2File = open("generated.fm2", "w")
   print >> fm2File, fm2Data.rstrip()
   
   from zipfile import ZipFile
   zf = ZipFile("rundata.zip")
   
   fighterRun = zf.open("FinalFantasy-LightWarriors-afternames.fm2")
   for line in fighterRun.readlines():
      print >> fm2File, line.rstrip()
   fighterRun.close()
   zf.close()
   fm2File.close()
   

if __name__ == '__main__':
   main()
